﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FastReflectionLib;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.Practices.SystemFacade;
using Fuse8.DomainFramework.Practices.SystemFacade.Extensions;

namespace Fuse8.DomainFramework.Practices.DataAccess.SqlRepository
{
    public class SqlMultiMapperSettings<TEntity>
    {
		public bool FirstSetIsEntity { get; set; }

        #region Private properties

		private readonly Dictionary<string, MappingProperty> _properties = new Dictionary<string, MappingProperty>();

		#endregion

        public void AddPropertyIndex<TParameter>(int index, Expression<Func<TEntity, object>> expression)
        {
        	AddPropertyIndex<TParameter>(index, expression.Body);
        }

		private void AddPropertyIndex<TParameter>(int index, Expression expression)
		{
			switch (expression.NodeType)
			{
				case ExpressionType.Call:
					var callExpression = (MethodCallExpression) expression;
					if (!SqlMultiMapperSettings.MultiMapMethodInfos.Value.Contains(callExpression.Method.GetGenericMethodDefinition().MethodHandle.Value))
					{
						throw new ArgumentOutOfRangeException("expression");
					}
			        var args = new List<Expression>(callExpression.Arguments);
			        args[0] = Expression.Convert(Expression.Constant(null), typeof (IEntityObject));

                    var f = ((UnaryExpression) callExpression.Arguments.Last()).Operand;

                    var parameters = ((IEnumerable<ParameterExpression>)f.GetType().GetProperty("Parameters").FastGetValue(f)).ToList();
                    parameters.Add((ParameterExpression)callExpression.Arguments[0]);
			        var body = (Expression) f.GetType().GetProperty("Body").FastGetValue(f);

			        var lambda = Expression.Lambda(body, parameters);

					var keyMatching =
						EntityGenerator.ConvertToRuntimeMethod(lambda);

					var listEntityType = callExpression.Method.GetGenericArguments()[1];

                    switch (args.Count)
			        {
                        case 3:
                            AddReader<TParameter>(callExpression.Arguments[1], Expression.Constant(null), keyMatching, index, listEntityType);
                            break;
                        case 4:
							AddReader<TParameter>(callExpression.Arguments[1], args[2], keyMatching, index, listEntityType);
                            break;
			            default:
                            throw new ArgumentOutOfRangeException("expression"); 
			        }
					break;
				default:
					throw new ArgumentOutOfRangeException("expression");
			}
		}

		public void AddProperties<TParameter>(Expression<Func<TEntity, object>> propertyExpression, int startIndex = 0)
		{
			if (propertyExpression.Body.NodeType != ExpressionType.New)
			{
				throw new ArgumentOutOfRangeException("propertyExpression");
			}
			var arguments = ((NewExpression) propertyExpression.Body).Arguments;
			for (var i = 0; i < arguments.Count; i++)
			{
				AddPropertyIndex<TParameter>(i + startIndex, arguments[i]);
			}
		}

		internal void SetProperty(string sp, int index, MappingProperty property)
        {
			_properties[GetKey(sp, index)] = property;
        }		
        
        internal MappingProperty GetProperty(string sp, int index)
        {
            var key = GetKey(sp, index);

            return GetProperty(key);
        }

    	internal MappingProperty GetProperty(string key)
    	{
    		return _properties.ContainsKey(key) ? _properties[key] : null;
    	}

    	internal bool IsRegisteredForParameter<TParameter>()
		{
			var sp = CommandNameFacade.GetCommandName<TEntity, TParameter>();
			return _properties.Any(p => p.Key.StartsWith(GetKey(sp, null)));
		}

        private void AddReader<TParameter>(Expression propertyExpression, Expression backPropertyExpression, MethodInfo keyMatching, int index, Type listEntityType)
		{
			_properties[GetKey(CommandNameFacade.GetCommandName<TEntity, TParameter>(), index)] = new MappingProperty
			                      	{
										Property = ((LambdaExpression)((UnaryExpression)propertyExpression).Operand).Body.PropertyInfo(),
										BackProperty = backPropertyExpression.NodeType == ExpressionType.Constant ? null : ((LambdaExpression)((UnaryExpression)backPropertyExpression).Operand).Body.PropertyInfo(),
			                      		Matching = keyMatching,
										ListEntityType = listEntityType
			                      	};
		}

		private static string GetKey (string sp, int? index)
		{
			return String.Format("{0}_{1}", sp, index);
		}        
    }

    internal class SqlMultiMapperSettings
    {
        public static readonly Lazy<IEnumerable<IntPtr>> MultiMapMethodInfos = new Lazy<IEnumerable<IntPtr>>(() =>
        {
            var expressions = new Expression<Func<IEntityObject, object>>[]{
               p => p.MultiMap<object, object>(x => new object[0], x => new { }, null),
               p => p.MultiMap<object, object>(x => new { }, x => new { }, null),
               p => p.MultiMap<object, object>(x => new object[0], x => new object[0], null),
               p => p.MultiMap<object, object>(x => new { }, x => new object[0], null),
               p => p.MultiMap<object, object>(x => new { }, null),
               p => p.MultiMap<object, object>(x => new object[0], null),
            };
            return
                expressions.Select(expression => ((MethodCallExpression)expression.Body).Method.GetGenericMethodDefinition().MethodHandle.Value).ToArray();
        });
    }
}